#
#    Copyright 2006 Andrew Wilkinson <aw@cs.york.ac.uk>.
#
#    This file is part of PyScobble (http://lastfm.indiegigs.co.uk/pyscrobble)
#
#    PyScobble is free software; you can redistribute it and/or modify
#    it under the terms of the GNU Lesser General Public License as published by
#    the Free Software Foundation; either version 2.1 of the License, or
#    (at your option) any later version.
#
#    PyScobble is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public License
#    along with PyScobble; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

import datetime
import md5
import xmlrpclib
from xml.sax.saxutils import escape as xml_escape

from scrobble.database import getUserProfile, getUserFriends, getUserNeighbours
from scrobble.utils import *

## A Last.fm User
#
# The username parameter must be passed, however the passwd field is optional and may be set at a later date
# using User.setPassword() if required.
#
# The password is currently only required when scrobbling tracks.
#
# @param username The username of the Last.fm to fetch.
# @param passwd The optional, unencoded password of the Last.fm user.
class User(object):
    ## This class constructor
    #
    # @param self The implicit self parameter
    # @param username The username of the Last.fm to fetch.
    # @param passwd The optional, unencoded password of the Last.fm user.
    def __init__(self, username, passwd=None, md5password=None):
        self.__username = username
        self.__passwd = passwd
        self.__md5passwd = md5password
        if self.__passwd and not self.__md5passwd:
            self.__md5passwd = md5.new(self.__passwd).hexdigest()

        self.__profile = {"username": username}

    ## Causes the given track to be submitted to Last.fm and added to this user's profile
    #
    # @throw NoPassword If the User's password has not been set then a NoPassword exception is raised.
    #
    # @param self The implicit self parameter
    # @param track A Track object representing the track to be scrobbled
    # @param time The time the track was listened to. If not included this defaults to now.
    def scrobble(self, track, time=None):
        return scrobbleTrack(self, track, time)

    ## Returns an HTML link to the user's profile page
    def getLink(self):
        return """<a href="http://www.last.fm/user/%s" title="%s's Last.fm profile">%s</a>""" % (self.username, self.name, self.username)

    def __getId(self):
        return self.__getProfileInfo("id")
    ## The User's id number.
    #
    # This is used internally by Last.fm.
    id = property(__getId)
    def __getCluster(self):
        return self.__getProfileInfo("cluster")
    ## The id of the cluster the User's profile is stored on.
    cluster = property(__getCluster)
    def __getUsername(self):
        return self.__getProfileInfo("username")
    ## The User's username.
    username = property(__getUsername)
    def __getPasswd(self):
        return self.__passwd
    def __setPasswd(self, value):
        self.__passwd = value
        self.__md5passwd = md5.new(value).hexdigest()
    ## The User's password, if set. None if not.
    passwd = property(__getPasswd, __setPasswd)
    def __getMD5Passwd(self):
        return self.__md5passwd
    def __setMD5Passwd(self, value):
        self.__md5passwd = value
    ## The User's password as an MD5 checksum, if set. None if not.
    md5passwd = property(__getMD5Passwd, __setMD5Passwd)
    def __getUrl(self):
        return self.__getProfileInfo("url")
    ## The url of the User's profile.
    url = property(__getUrl)
    def __getRealname(self):
        return self.__getProfileInfo("realname")
    ## The User's real name.
    #
    # This is optional and returns None if not set.
    realname = property(__getRealname)
    def __getMbox_sha1sum(self):
        return self.__getProfileInfo("mbox_sha1sum")
    ## The mbox_sha1sum parameter returned by Last.fm
    #
    # It is unclear what this means or what it is useful for.
    mbox_sha1sum = property(__getMbox_sha1sum)
    def __getHomepage(self):
        return self.__getProfileInfo("homepage")
    ## The url of the homepage the User specified.
    #
    # None if not set.
    homepage = property(__getHomepage)
    def __getRegistered(self):
        return self.__getProfileInfo("registered")
    ## The date the User registered.
    registered = property(__getRegistered)
    def __getStatsreset(self):
        return self.__getProfileInfo("statsreset")
    ## The date the User reset their statistics.
    #
    # None if they have not been reset.
    statsreset = property(__getStatsreset)
    def __getAge(self):
        return self.__getProfileInfo("age")
    ## The age, in years, of the User.
    #
    # None if they have not specified a birthday.
    age = property(__getAge)
    def __getCountry(self):
        return self.__getProfileInfo("country")
    ## The User's country.
    #
    # None if not specified.
    country = property(__getCountry)
    def __getPlaycount(self):
        return self.__getProfileInfo("playcount")
    ## The number of tracks the User has scrobbled.
    playcount = property(__getPlaycount)

    def __getName(self):
        try:
            return self.__getProfileInfo("realname")
        except AttributeError:
            return self.__getProfileInfo("username")
    ## A convinence function which is guarenteed to return a human readable name for the User.
    #
    # Last.fm User's do not have to given their name, this function returns the name they have
    # given, or their username if they don't give one.
    name = property(__getName)

    def __getFriends(self):
        if self.__profile.has_key("friends"):
            return self.__profile["friends"]

        friends = getUserFriends(self.__username)
        self.__profile["friends"] = [User(f) for f in friends]
        return self.__profile["friends"]

    ## A list of the User's friends
    #
    # This member contains a list of User objects - one for each friend.
    friends = property(__getFriends)

    def __getNeighbours(self):
        if self.__profile.has_key("neighbours"):
            return self.__profile["neighbours"]

        neighbours = getUserNeighbours(self.__username)
        self.__profile["neighbours"] = [User(f) for f in neighbours]
        return self.__profile["neighbours"]

    ## A list of the User's neighbours
    #
    # This member contains a list of User objects - one for each neighbour the User has.
    neighbours = property(__getNeighbours)

    def __getRecentTracks(self):
        dom = getDom(audioscrobbler_webservices, "/1.0/user/%s/recenttracks.xml" % (self.username, ))

        r = []
        recenttracks = dom.getElementsByTagName("recenttracks")[0]
        for track in recenttracks.getElementsByTagName("track"):
            artist = getDomText(track.getElementsByTagName("artist")[0].childNodes)
            artist_mbid = track.getElementsByTagName("artist")[0].getAttribute("mbid")
            if artist_mbid == "":
                artist_mbid = None
            name = getDomText(track.getElementsByTagName("name")[0].childNodes)
            mbid = getDomText(track.getElementsByTagName("mbid")[0].childNodes)
            if mbid == "":
                mbid = None
            date = datetime.datetime.fromtimestamp(int(track.getElementsByTagName("date")[0].getAttribute("uts")))
            r.append((Track(artist=Artist(name=artist, mbid=artist_mbid), name=name, mbid=mbid), date))
        return r

    ## A list of pairs containing the most recently scrobbled tracks, and the time they were scrobbled.
    #
    # This member contains a list of pairs, each pair containing a Track and a datetime object.
    #
    # @attention Accessing this member causes a hit to the Last.fm webserver's each time it accessed.
    # It is recommended that this member is read into a local variable and that variable is used.
    recent_tracks = property(__getRecentTracks)

    def __getTopArtists(self):
        dom = getDom(audioscrobbler_webservices, "/1.0/user/%s/topartists.xml" % (self.username, ))

        r = []
        topartists = dom.getElementsByTagName("topartists")[0]
        for artist in topartists.getElementsByTagName("artist"):
            name = getDomText(artist.getElementsByTagName("name")[0].childNodes)
            mbid = getDomText(artist.getElementsByTagName("mbid")[0].childNodes)
            if mbid == "":
                mbid = None
            rank = getDomText(artist.getElementsByTagName("rank")[0].childNodes)
            playcount = getDomText(artist.getElementsByTagName("playcount")[0].childNodes)
            r.append((Artist(name=name, mbid=mbid), rank, playcount))

        return r

    ## A list containing the top artist, the rank and the playcount.
    #
    # This member contains a list containing the top artist, the rank and the playcount.
    #
    # @attention Accessing this member causes a hit to the Last.fm webserver's each time it accessed.
    # It is recommended that this member is read into a local variable and that variable is used.
    top_artists = property(__getTopArtists)

    def __getTopTags(self):
        dom = getDom(audioscrobbler_webservices, "/1.0/user/%s/tags.xml" % (self.username, ))

        r = []
        toptags = dom.getElementsByTagName("toptags")[0]
        for tag in toptags.getElementsByTagName("tag"):
            name = getDomText(tag.getElementsByTagName("name")[0].childNodes)
            count = getDomText(tag.getElementsByTagName("count")[0].childNodes)
            url = getDomText(tag.getElementsByTagName("url")[0].childNodes)
            r.append((name, count, url))

        return r

    ## A list containing the top tags, the usage count and an url to the corresponding tag.
    #
    # This member contains a list containing the top tags, the usage cound and an url to the corresponding tag.
    #
    # @attention Accessing this member causes a hit to the Last.fm webserver's each time it accessed.
    # It is recommended that this member is read into a local variable and that variable is used.
    top_tags = property(__getTopTags)

    def __getProfileInfo(self, attr):
        if not self.__profile.has_key(attr):
            self.__profile.update(getUserProfile(self.username))
        try:
            return self.__profile[attr]
        except KeyError:
            raise AttributeError, "No attribute %s" % (attr, )

    def tag(self, item, tag, mode="append"):
        if not self.__md5passwd:
            raise NoPassword, "Tagging requires a password."
        assert isinstance(tag, str) or (isinstance(item, list) and False not in [isinstance(x, str) for x in item])
        assert mode == "append" or mode == "set"
        if isinstance(tag, str):
            tag = [tag]

        s = xmlrpclib.ServerProxy("http://ws.audioscrobbler.com/1.0/rw/xmlrpc.php")

        challenge = getChallenge()

        if isinstance(item, Artist):
            s.tagArtist(
                self.username,
                challenge,
                md5.new('%s%s' % (self.md5passwd, challenge)).hexdigest(),
                xml_escape(item.name),
                [xml_escape(t) for t in tag],
                mode
            )
        elif isinstance(item, Album):
            s.tagAlbum(
                self.username,
                challenge,
                md5.new('%s%s' % (self.md5passwd, challenge)).hexdigest(),
                xml_escape(item.artist.name),
                xml_escape(item.name),
                [xml_escape(t) for t in tag],
                mode
            )
        elif isinstance(item, Track):
            s.tagTrack(
                self.username,
                challenge,
                md5.new('%s%s' % (self.md5passwd, challenge)).hexdigest(),
                xml_escape(item.artist.name),
                xml_escape(item.name),
                [xml_escape(t) for t in tag],
                mode
            )
        else:
            raise TypeError, "item is of type %s not Artist, Album or Track." % (type(item), )

    ## Returns a hash of the User's username
    def __hash__(self):
        return hash(self.username)
    ## Compares two User objects
    def __cmp__(self, other):
        return cmp(self.username, other.username)

    ## Returns a string representation of the User
    def __str__(self):
        return "%s" % (self.username, )
    ## Returns a string representation of the User
    def __repr__(self):
        return "<scrobble.User(%s)" % (repr(self.username), )

from scrobble.submit import scrobbleTrack
from scrobble.artist import Artist
from scrobble.album import Album
from scrobble.track import Track
